<template>
  <div id="chart"></div>
</template>

<script>
import G6 from "@antv/g6";
export default {
  name: "Chart",
  props: {
    defaultContextId: {
      type: Number,
      default() {
        return 0;
      }
    }
  },
  data: () => {
    return {
      loading: false,
      tree: [],
      chartObj: null
    };
  },
  mounted() {},
  methods: {
    saveFullImage() {
      function downloadFile(content, fileName) {
        var base64ToBlob = function(code) {
          let parts = code.split(";base64,");
          let contentType = parts[0].split(":")[1];
          let raw = window.atob(parts[1]);
          let rawLength = raw.length;
          let uInt8Array = new Uint8Array(rawLength);
          for (let i = 0; i < rawLength; ++i) {
            uInt8Array[i] = raw.charCodeAt(i);
          }
          return new Blob([uInt8Array], {
            type: contentType
          });
        };
        let aLink = document.createElement("a");
        let blob = base64ToBlob(content);
        let evt = document.createEvent("HTMLEvents");
        evt.initEvent("click", true, true);
        aLink.download = fileName;
        aLink.href = URL.createObjectURL(blob);
        aLink.click();
      }
      this.chartObj.toFullDataURL(res => {
        downloadFile(res, "123.png");
      }, "image/png");
    },
    create(tree, focus_context_id) {
      let self = this;
      this.loading = true;
      this.tree = tree;
      this.chartObj && this.chartObj.destroy();
      const { clientWidth, clientHeight } = document.getElementById("chart");

      const defaultNodeStyle = {
        fill: "#91d5ff",
        stroke: "#40a9ff",
        radius: 5
      };

      const defaultEdgeStyle = {
        stroke: "#91d5ff",
        radius: 10,
        offset: 30,

        controlPoints: [{ x: 10, y: 20 }],
        endArrow: true
      };

      const defaultLayout = {
        type: "compactBox",
        direction: "LR",
        nodeSep: 50,
        rankSep: 100,
        getId: function getId(d) {
          return d.context_id;
        },
        getHeight: function getHeight() {
          return 16;
        },
        getWidth: function getWidth() {
          return 100;
        },
        getVGap: function getVGap() {
          return 40;
        },
        getHGap: function getHGap() {
          return 70;
        }
      };

      const defaultLabelCfg = {
        style: {
          fill: "#000",
          fontSize: 12
        }
      };

      G6.registerEdge("flow-line", {
        draw(cfg, group) {
          const startPoint = cfg.startPoint;
          const endPoint = cfg.endPoint;
          const k =
            (1.732 * (endPoint.x - startPoint.x) +
              3 * (endPoint.y - startPoint.y)) /
            12;
          const { style } = cfg;
          const shape = group.addShape("path", {
            attrs: {
              stroke: style.stroke,
              endArrow: style.endArrow,
              path: [
                ["M", startPoint.x + 10, startPoint.y + 6],
                ["L", startPoint.x + 1.732 * k, startPoint.y + k],
                ["L", endPoint.x - 1.732 * k, endPoint.y - k],
                ["L", endPoint.x - 30, endPoint.y - 18]
              ]
            }
          });

          return shape;
        }
      });
      G6.registerNode(
        "icon-node",
        {
          options: {
            size: [60, 20],
            stroke: "#ff002f",
            fill: "#91d5ff"
          },

          draw(cfg, group) {
            const w = 40;
            const h = 40;

            const getPath = (px, py) => {
              const path = [
                ["M", px, py - h / 2],
                ["L", px + 0.46 * w, py - h / 4],
                ["L", px + 0.46 * w, py + h / 4],
                ["L", px, py + h / 2],
                ["L", px - 0.46 * w, py + h / 4],
                ["L", px - 0.46 * w, py - h / 4],
                ["Z"]
              ];
              return path;
            };
            const getColor = () => {
              if (cfg.info.blocked) {
                return "#ff0000";
              }
              if (cfg.threats && cfg.threats.length) {
                return "#ff6200";
              }
              return "#595959";
            };
            const keyShape = group.addShape("path", {
              attrs: {
                path: getPath(0, 0),
                fill: getColor(),
                stroke: "#eee",
                width: 40,
                height: 40,
                x: -w / 2 - 15,
                y: -h / 2 - 15
              }
            });

            if (cfg.context_id === focus_context_id) {
              self.$emit("change-node", cfg);
            }
            const getIcon = () => {
              if (cfg.info.blocked) {
                return require("@/assets/icon/close-circle-fill.png");
              }
              if (cfg.threats && cfg.threats.length) {
                return require("@/assets/icon/error-warning-line.png");
              }
              return require("@/assets/icon/checkbox-circle-line.png");
            };
            group.addShape("image", {
              attrs: {
                x: -w / 2 + 5 / 2,
                y: -h / 2 + 6 / 2,
                width: w - 5,
                height: h - 6,
                img: getIcon()
              },
              name: "image-shape"
            });

            let counts = {};
            try {
              const {
                actions: {
                  count: { disk, net, reg }
                }
              } = cfg;
              counts = { disk, net, reg };
            } catch {
              counts = {};
            }

            let file_width = false,
              net_width = false;
            counts.disk
              ? (file_width = self.drawFile(group, cfg))
              : (file_width = -90);
            counts.net
              ? (net_width = self.drawNet(group, cfg, file_width))
              : (net_width = -90);
            let drawRegWdith = net_width === -90 ? file_width : net_width;

            counts.reg ? self.drawReg(group, cfg, drawRegWdith) : "";

            const process_name = cfg.info.process_name;
            const text = group.addShape("text", {
              attrs: {
                text: process_name,
                x: 0,
                y: 0,
                textAlign: "middle",
                textBaseline: "middle",
                fill: "#666"
              },
              name: "text-shape"
            });
            const bbox = text.getBBox();
            text.attr({
              x: -bbox.width / 2,
              y: -38
            });

            return keyShape;
          },
          getAnchorPoints: function getAnchorPoints() {
            return [
              [0, 0.5],
              [1, 0.5]
            ];
          }
        },
        "rect"
      );

      const minimap = new G6.Minimap({
        size: [150, 100]
      });
      const graph = new G6.TreeGraph({
        container: "chart",
        width: clientWidth,
        height: clientHeight,
        linkCenter: true,
        plugins: [minimap],
        animate: true,
        animateCfg: {
          duration: 3000,
          easing: "linearEasing"
        },
        modes: {
          default: [
            "drag-canvas",
            "zoom-canvas",
            "drag-node",
            {
              type: "tooltip",
              formatText(model) {
                const threat = model.threats ? model.threats[0] : undefined;
                const info = model.info;
                const count = model.actions
                  ? model.actions.count
                  : { net: 0, disk: 0, reg: 0 };
                let threattext = "";
                let threatLevel = info.blocked ? "高" : threat ? "中" : "低";
                if (threat) {
                  const keys = Object.keys(threat);
                  keys.map(key => {
                    threattext =
                      threattext + `<tr><td>${key}</td><td>${threat[key]}</td>`;
                  });
                }
                const html = document.createElement("div");
                html.innerHTML = `
                <table class="node-detail">
                  <tr>
                    <td style="width:100px">威胁程度</td>
                    <td>${threatLevel}</td>
                  </tr>
                  <tr>
                    <td>已阻止</td>
                    <td>${info.blocked ? "是" : "否"}</td>
                  </tr>
                  <tr>
                    <td>运行状态</td>
                    <td>${info.status}</td>
                  </tr>
                  ${threattext}
                </table>
                <table class="node-detail process-active">
                  <tr><td>进程操作</td><td></td></tr>
                  <tr>
                    <td>网络操作数</td>
                    <td>${count.net}</td>
                  </tr>
                  <tr>
                    <td>文件操作数</td>
                    <td>${count.disk}</td>
                  </tr>
                  <tr>
                    <td>注册表操作数</td>
                    <td>${count.reg}</td>
                  </tr>
                </table>`;
                return html.innerHTML;
              },
              shouldUpdate: e => true
            }
          ]
        },
        defaultNode: {
          type: "icon-node",
          size: [120, 40],
          style: defaultNodeStyle,
          labelCfg: defaultLabelCfg
        },
        defaultEdge: {
          type: "flow-line",
          style: defaultEdgeStyle
        },
        nodeStateStyles: {
          hover: {
            stroke: "#fd0",
            lineWidth: 3
          },
          clicked: {
            stroke: "#e00",
            lineWidth: 3
          }
        },
        layout: defaultLayout
      });

      graph.data(this.tree);
      graph.on("afterrender", () => {});
      graph.render();
      G6.Util.traverseTree(this.tree, d => {
        const tx = d.x;
        const ty = d.y;
        d.x = (tx + ty) * 0.866;
        d.y = -(ty - tx) / 2;
        return true;
      });
      graph.fitView([-380, -80, 420, 120], undefined, true);

      graph.on("node:mouseenter", function(e) {
        graph.setItemState(e.item, "hover", true);
      }),
        graph.on("node:mouseleave", function(e) {
          graph.setItemState(e.item, "hover", false);
        });
      graph.on("node:click", function(e) {
        e.currentTarget.cfg.nodes.forEach(item => {
          graph.setItemState(item, "clicked", false);
        });

        graph.setItemState(e.item, "clicked", true);
      });
      graph.on("node:click", evt => {
        const { item } = evt;
        self.$emit("change-node", item._cfg.model);
      });
      this.loading = false;
      this.chartObj = graph;
    },
    drawReg(group, cfg, w) {
      const reg = group.addShape("rect", {
        attrs: {
          stroke: "#666",
          x: 200,
          y: 0,
          width: 20,
          height: 20
        }
      });
      const regImage = group.addShape("image", {
        attrs: {
          img: require("@/assets/path/reg.svg"),
          width: 20,
          height: 20
        }
      });
      const regText = group.addShape("text", {
        attrs: {
          text: String(cfg.actions.count.reg),
          x: 20,
          y: 20,
          textAlign: "left",
          textBaseline: "middle",
          fill: "#666"
        },
        name: "reg-text"
      });
      const regImage_box = regImage.getBBox();
      const regBox_text = regText.getBBox();
      reg.attr({
        x: w,
        y: -regImage_box.height / 2 + 40,
        width: regImage_box.width + regBox_text.width + 10
      });
      regImage.attr({
        x: w,
        y: -regImage_box.height / 2 + 40
      });
      regText.attr({
        x: w + regImage_box.width + 5,
        y: 42
      });
    },
    drawNet(group, cfg, w) {
      const net = group.addShape("rect", {
        attrs: {
          stroke: "#666",
          x: 200,
          y: 0,
          width: 20,
          height: 20
        }
      });
      const netImage = group.addShape("image", {
        attrs: {
          img: require("@/assets/path/net.svg"),
          width: 20,
          height: 20
        }
      });
      const netText = group.addShape("text", {
        attrs: {
          text: String(cfg.actions.count.net),
          x: 0,
          y: 0,
          textAlign: "left",
          textBaseline: "middle",
          fill: "#666"
        },
        name: "net-text"
      });
      const netImage_box = netImage.getBBox();
      const netBox_text = netText.getBBox();
      net.attr({
        x: w,
        y: -netImage_box.height / 2 + 40,
        width: netImage_box.width + netBox_text.width + 10
      });

      netImage.attr({
        x: w,
        y: -netImage_box.height / 2 + 40
      });
      netText.attr({
        x: w + netImage_box.width + 5,
        y: 42
      });
      return w + net.attrs.width;
    },
    drawFile(group, cfg) {
      const file = group.addShape("rect", {
        attrs: {
          stroke: "#666",
          x: 0,
          y: 0,
          width: 20,
          height: 20
        }
      });

      const fileImage = group.addShape("image", {
        attrs: {
          img: require("@/assets/path/file.svg"),
          width: 20,
          height: 20
        }
      });

      const fileText = group.addShape("text", {
        attrs: {
          text: String(cfg.actions.count.disk),
          x: 0,
          y: 0,
          textAlign: "left",
          textBaseline: "middle",
          fill: "#666"
        },
        name: "file-text"
      });

      const fileBox_image = fileImage.getBBox();
      const fileBox_text = fileText.getBBox();
      file.attr({
        x: -fileBox_image.width / 2 - 30,
        y: -fileBox_image.height / 2 + 40,
        width: fileBox_image.width + fileBox_text.width + 10
      });
      fileImage.attr({
        x: -fileBox_image.width / 2 - 30,
        y: 30
      });
      fileText.attr({
        x: -fileBox_image.width * 2 + 25,
        y: 42
      });
      return -fileBox_image.width / 2 - 30 + file.attrs.width;
    }
  }
};
</script>

<style lang="scss" scoped>
#chart {
  width: 100%;
  height: 100%;
}
</style>
<style>
.node-detail {
  padding: 10px 6px;
  width: 500px;
  color: #444;
  background-color: rgba(255, 255, 255, 0.9);
  border: 1px solid #e2e2e2;
  border-radius: 4px;
}
.process-active {
  width: 300px;
}
</style>
